package conf2.ui;

import conf2.iface.IGamePieceListener;
import conf2.iface.IPieceSelectionListener;
import conf2.model.EActor;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.LinkedList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.border.Border;

public abstract class GamePiece extends JComponent
        implements MouseListener, MouseMotionListener
{

    private Point clickPoint;
    private boolean wasDragged = false;
    private boolean mouseDown = false;
    private final Color normalBg,  hoverBg,  clickBg,  normalFrame,  normalText,  clickFrame,  clickText,  markBg;
    private Color currTextColor,  currBgColor;
    private final Border normalBorder,  clickBorder;
    private final String text;
    private final EActor actor;
    private Point lastPos = null;
    private boolean movable = true;
    private boolean isMarked = false;
    private boolean isKnown = true;
    private boolean isHovered = false;
    private boolean selectionMode = false;
    private boolean selectable = false;
    private final Color selectableColor = new Color(0, 255, 0, 96),  selectableColorHover = new Color(0, 255, 0, 192);
    public static final int PIECE_WIDTH = 45,  PIECE_HEIGHT = 25;

    // General mouse interaction listener.
    private List<IGamePieceListener> listeners = new LinkedList<IGamePieceListener>();
    // Listens to clicks when in piece selection mode (for Recall to Mordor).
    private IPieceSelectionListener selectionListener = null;

    public GamePiece(
            EActor actor, String text, Color normalBg, Color hoverBg, Color clickBg,
            Color normalFrame, Color normalText, Color clickFrame,
            Color clickText, Color markBg)
    {
        this.actor = actor;
        this.text = text;
        this.normalBg = normalBg;
        this.hoverBg = hoverBg;
        this.clickBg = clickBg;
        this.normalFrame = normalFrame;
        this.normalText = normalText;
        this.clickFrame = clickFrame;
        this.clickText = clickText;
        this.markBg = markBg;

        currTextColor = normalText;

        addMouseListener(this);
        addMouseMotionListener(this);

        normalBorder = BorderFactory.createLineBorder(normalFrame);
        clickBorder = BorderFactory.createLineBorder(clickFrame);

        setBorder(normalBorder);
        currBgColor = normalBg;
        setFont(FontSet.getGamePiece());

        setSize(PIECE_WIDTH, PIECE_HEIGHT);
        remember();
    }

    public void setNormalBackground()
    {
        currBgColor = normalBg;
        repaint();
    }

    public void setMovable(boolean b)
    {
        movable = b;
    }

    public EActor getActor()
    {
        return actor;
    }

    public void addGamePieceListener(IGamePieceListener l)
    {
        listeners.add(l);
    }

    public boolean removeGamePieceListener(IGamePieceListener l)
    {
        return listeners.remove(l);
    }

    public void mark()
    {
        if (!isMarked) {
            currBgColor = markBg;
            repaint();
            isMarked = true;
        }
    }

    public void unmark()
    {
        if (isMarked) {
            currBgColor = normalBg;
            repaint();
            isMarked = false;
        }
    }

    public boolean isMarked()
    {
        return isMarked;
    }

    /**
     * Sets whether the piece's name should be visible on the map (known),
     * or replaced by a '?' (not known).
     * @param b
     */
    public void setKnown(boolean b)
    {
        isKnown = b;
    }

    public boolean isKnown()
    {
        return isKnown;
    }

    public void setPieceSelectionListener(IPieceSelectionListener pieceSelectionListener)
    {
        selectionListener = pieceSelectionListener;
    }

    public void remember()
    {
        lastPos = getLocation();
    }

    public void snapback()
    {
        if (lastPos != null) {
            setLocation(lastPos);
        }
    }

    void setSelectionMode(boolean b)
    {
        selectionMode = b;
        selectable = false;
    }

    /**
     * Sets a piece as selectable, for use with the piece selection mode
     * of PieceCollection.
     * @param b
     */
    void setSelectable(boolean b)
    {
        selectable = b;
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        Dimension size = getSize();
        g.setColor(currBgColor);
        g.fillRect(0, 0, size.width, size.height);

        String s = text;
        if (!isKnown) {
            s = "?";
        }

        g.setColor(currTextColor);
        g.setFont(getFont());
        int tw = g.getFontMetrics().stringWidth(s);
        int tx = (getWidth() - tw) / 2;
        int ty = g.getFontMetrics().getHeight();
        g.drawString(s, tx, ty);

        if (selectable) {
            if (isHovered) {
                g.setColor(selectableColorHover);
            } else {
                g.setColor(selectableColor);
            }
            g.fillRect(0, 0, size.width, size.height);
        }
    }

    public void mouseClicked(MouseEvent e)
    {
//        System.out.println("CLICK");
//        for (IGamePieceListener listener : listeners) {
//            if (isKnown) {
//                if (e.getButton() == MouseEvent.BUTTON1) {
//                    listener.pieceClicked(this, e.getClickCount() > 1, e.getModifiersEx());
//                } else {
//                    listener.pieceAltClicked(this, e.getClickCount() > 1, e.getModifiersEx());
//                }
//            }
//            listener.pieceReleased(this);
//        }
    }

    public void mousePressed(MouseEvent e)
    {
        System.out.println("PRESS");
        if (selectionMode && selectable) {
            selectionListener.pieceSelected(actor);
        } else if (isKnown && !selectionMode) {
            mouseDown = true;
            clickPoint = e.getPoint();
            setBorder(clickBorder);
            currBgColor = clickBg;
            currTextColor = clickText;

            for (IGamePieceListener listener : listeners) {
                if (e.getButton() == MouseEvent.BUTTON1) {
                    listener.pieceClicked(this, e.getClickCount() > 2, e.getModifiersEx());
                } else {
                    listener.pieceAltClicked(this, e.getClickCount() > 2, e.getModifiersEx());
                }
            }
            repaint();
        }
    }

    public boolean isMovable()
    {
        return movable;
    }

    public void mouseReleased(MouseEvent e)
    {
        System.out.println("RELEASE");
        for (IGamePieceListener listener : listeners) {
            listener.pieceReleased(this);
        }
        if (isKnown && !selectionMode) {
            mouseDown = false;
            setBorder(normalBorder);
            currBgColor = normalBg;

            currTextColor = normalText;
            if (wasDragged && movable) {
                Point pPanel = getLocation();
                Point pMouse = e.getPoint();
                pPanel.x += pMouse.x;
                pPanel.y += pMouse.y;
                for (IGamePieceListener listener : listeners) {
                    listener.pieceDropped(this, pPanel);
                }
                currBgColor = hoverBg;
            }
            repaint();
        }
        wasDragged = false;
    }

    public void mouseEntered(MouseEvent e)
    {

        isHovered = true;
        if (isKnown && !mouseDown && !selectionMode) {
            currBgColor = hoverBg;
        }

        repaint();
    }

    public void mouseExited(MouseEvent e)
    {
        isHovered = false;
        if (isKnown && !mouseDown && !selectionMode) {
            currBgColor = normalBg;
        }
        repaint();
    }

    public void mouseDragged(MouseEvent e)
    {
        System.out.println("DRAG");
        if (movable && !selectionMode && e.getButton() == MouseEvent.BUTTON1) {
            Point loc = this.getLocation();
            loc.x += e.getX();
            loc.y += e.getY();

            wasDragged = true;

            Point p = e.getPoint();
            Point np = getLocation();

            int dx = p.x - clickPoint.x;
            int dy = p.y - clickPoint.y;
            np.x += dx;
            np.y += dy;
            setLocation(np);

            Point reportPoint = new Point(np);
            reportPoint.x += p.x;
            reportPoint.y += p.y;
            for (IGamePieceListener listener : listeners) {
                listener.pieceDragged(this, reportPoint);
            }
        }
    }

    public void mouseMoved(MouseEvent e)
    {
        // do nothing
    }

    @Override
    public String toString()
    {
        return actor.toString() + ", loc: " + getLocation().toString() + ", vis: " + isVisible() + ", known: " + isKnown + ", resp: " + movable;
    }
}

